package com.xiyou.rmls.service.guiderule.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xiyou.rmls.constants.rule.RuleConstant;
import com.xiyou.rmls.model.dto.common.DrlInfoDto;
import com.xiyou.rmls.model.dto.common.TemplateDrlInfoDto;
import com.xiyou.rmls.model.vo.guiderule.*;
import com.xiyou.rmls.service.guiderule.GuideRuleInfoService;
import com.xiyou.rmls.utils.DroolsUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Service
public class GuideRuleInfoServiceImpl implements GuideRuleInfoService {

    @Value("${rmls.freemarker.group-rule.path}")
    private String ruleGroupFtlPath;

    @Value("${rmls.freemarker.group-rule.name}")
    private String ruleGroupFtlName;

    @Value("${rmls.freemarker.guide-rule.path}")
    private String guideRuleFtlPath;

    @Value("${rmls.freemarker.guide-rule.name}")
    private String guideRuleFtlName;

    /**
     * 生成引导式规则的规则文件--drl文件
     *
     * @param guideRuleInfoParamVo
     * @return
     */
    public String buildGuideRuleDrlInfo(GuideRuleInfoParamVo guideRuleInfoParamVo) {
        // 得到区块信息
        List<RuleBlockVo> ruleBlockVoList = guideRuleInfoParamVo.getRuleBlockVoList();
        // 定义规则的自增序列
        int rule_index = 1;
        // 规则名称前缀
        String preRuleName = guideRuleInfoParamVo.getGuideRuleInfoVo().getRuleCode()
                + "_"
                + guideRuleInfoParamVo.getGuideRuleInfoVo().getRuleVersion();
        // 定义列表存储drl信息
        List<DrlInfoDto> drlInfoDtoList = Lists.newArrayList();
        // 定义优先级
        int priority = 1;
        // 开始遍历
        for (RuleBlockVo ruleBlockVo : ruleBlockVoList) {
            // 定义当前规则的规则活跃名称
            String ruleName = preRuleName + "_" + rule_index++;
            // 用来封装drl信息
            DrlInfoDto drlInfoDto = new DrlInfoDto();
            // 赋值规则名称
            drlInfoDto.setRuleName(ruleName);
            // 记录当前的规则名称_规则版本
            drlInfoDto.setPreRuleName(preRuleName);
            // 赋值优先级
            if (!Objects.isNull(drlInfoDto.getPriority())) {
                priority = drlInfoDto.getPriority();
            } else {
                drlInfoDto.setPriority(priority - 1);
            }
            // 处理属性
            List<String> attributeList = Lists.newArrayList();
            attributeList.add("salience " + priority);
            attributeList.add("no-loop true");
            drlInfoDto.setAttributeList(attributeList);
            // 处理条件信息 即LHS
            dealRuleLhs(ruleBlockVo.getGuideRuleConditionInfoVoList(), drlInfoDto);
            // 处理结果信息 即RHS
            dealRuleRhs(ruleBlockVo.getGuideRuleOutputVoList(), drlInfoDto);
            // 添加到列表中
            drlInfoDtoList.add(drlInfoDto);
        }
        // 处理未命中的输出信息
        drlInfoDtoList.add(dealNoHitOutputInfo(guideRuleInfoParamVo.getNoHitStrategyOutputVoList(), preRuleName));

        // 封装初始化freemarker的返回值
        TemplateDrlInfoDto templateDrlInfoDto = new TemplateDrlInfoDto();
        // 赋值规则编号和版本号
        templateDrlInfoDto.setRuleModel(guideRuleInfoParamVo.getGuideRuleInfoVo().getRuleCode());
        templateDrlInfoDto.setRuleVersion(guideRuleInfoParamVo.getGuideRuleInfoVo().getRuleVersion());
        templateDrlInfoDto.setPreRuleName(preRuleName);
        templateDrlInfoDto.setDrlInfoDtoList(drlInfoDtoList);

        // 组装drl信息
        String drlStr = initDrlStr(templateDrlInfoDto);
        return drlStr;
    }

    /**
     * 生成drl字符串
     *
     * @param templateDrlInfoDto 封装好的对象
     * @return
     */
    private String initDrlStr(TemplateDrlInfoDto templateDrlInfoDto) {
        // 封装参数
        Map<String, Object> initParamMap = Maps.newHashMap();
        initParamMap.put("guideRuleList", templateDrlInfoDto);
        initParamMap.put("templateDrlInfo", templateDrlInfoDto);
        // 最终的drl字符
        String drl = StringUtils.EMPTY;
        // 用对应的对象开始生成drl
        try {
            drl = DroolsUtils.initRuleTemplate(guideRuleFtlPath, guideRuleFtlName, initParamMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return drl;
    }

    /**
     * 处理未命中的输出信息
     *
     * @param noHitStrategyOutputVoList 未命中的输出列表
     * @param activationGroupName
     * @return
     */
    private DrlInfoDto dealNoHitOutputInfo(List<GuideRuleOutputVo> noHitStrategyOutputVoList,
                                           String activationGroupName) {
        // 处理未命中的输出
        // 新建一个drlInfo对象
        DrlInfoDto drlInfoDto = new DrlInfoDto();
        // 赋值规则名称
        drlInfoDto.setRuleName(activationGroupName + "_No_Hit");
        // 赋值属性列表
        List<String> attributeList = Lists.newArrayList();
        attributeList.add("salience -9999");
        attributeList.add("no-loop true");
        drlInfoDto.setAttributeList(attributeList);
        // 赋值then
        dealRuleRhs(noHitStrategyOutputVoList, drlInfoDto);
        return drlInfoDto;
    }

    /**
     * 处理规则文件的RHS部分
     *
     * @param guideRuleOutputVoList 输出部分
     * @param drlInfoDto 需要将处理好的结果封装在这里
     */
    private void dealRuleRhs(List<GuideRuleOutputVo> guideRuleOutputVoList, DrlInfoDto drlInfoDto) {
        // 开始遍历输出的列表
        for (GuideRuleOutputVo guideRuleOutputVo : guideRuleOutputVoList) {
            // 判断是否有条件
            GuideRuleConditionInfoVo outCondition = guideRuleOutputVo.getGuideRuleConditionInfoVo();
            // 格式化then的输出字段值
            String thenValue = DroolsUtils.formatFiledValueByType(guideRuleOutputVo.getFieldType(),
                    guideRuleOutputVo.getFieldValue());
            // 如果当前的输出为空 则表示没有条件直接输出
            String rhsStr = "$insertMap.put(\""
                    + guideRuleOutputVo.getFieldEn()
                    + "\", "
                    + thenValue
                    + ");";
            if (!Objects.isNull(outCondition)) {
                // 带有输出条件的输出
                // 需要拼装if的
                List<String> outConditionList = Lists.newArrayList();
                for (GuideRuleConditionInfoVo child : outCondition.getChildren()) {
                    // 格式化字段，因为我们比较的时候没有办法用Object类型与int等类型比较
                    outConditionList.add(DroolsUtils.formatThenCondition(child));
                }
                // 拼接出来了if条件
                String ifRhsStr = StringUtils.join(outConditionList, " " + outCondition.getLogical() + " ");
                // 拼接if
                rhsStr = "if (" + ifRhsStr + ") {" + rhsStr + "}";
            }
            // 如果输出不为空
            if (StringUtils.isNotBlank(rhsStr)) {
                drlInfoDto.getThenStack().push(rhsStr);
            }
        }
    }

    /**
     * 处理规则文件的LHS部分
     *  @param guideRuleConditionInfoVoList 条件部分
     * @param drlInfoDto 需要将处理好的封装在这里
     */
    private void dealRuleLhs(List<GuideRuleConditionInfoVo> guideRuleConditionInfoVoList,
                             DrlInfoDto drlInfoDto) {
        // 开始遍历
        for (GuideRuleConditionInfoVo guideRuleConditionInfoVo : guideRuleConditionInfoVoList) {
            String conditionType = guideRuleConditionInfoVo.getConditionType();
            switch (conditionType) {
                case RuleConstant.RULE_TYPE:
                    // 简单规则
                    // 生成一个规则 直接放进去
                    dealWithSimpleRules(guideRuleConditionInfoVo, drlInfoDto);
                    break;
                case RuleConstant.RELATION_TYPE:
                    // 关系
                    dealWithRelationshipRules(guideRuleConditionInfoVo, drlInfoDto);
                    break;
                case RuleConstant.CIRCULATE_RELATION_TYPE:
                    // todo 循环先没有写
                    break;
                case RuleConstant.CONDITION_GROUP_TYPE:
                    // 规则组
                    dealWithGroupRules(guideRuleConditionInfoVo, drlInfoDto);
                    break;
                default:
                    throw new IllegalStateException("当前暂时不支持此规则类型: " + conditionType);
            }
        }
    }

    /**
     * 处理简单规则
     *
     * @param guideRuleConditionInfoVo 规则条件对象
     * @param drlInfoDto drl对象
     */
    private void dealWithSimpleRules(GuideRuleConditionInfoVo guideRuleConditionInfoVo, DrlInfoDto drlInfoDto) {
        // 先简单生成: MM_AGE = 12
        StringBuilder sb = new StringBuilder();
        sb.append("this[\"" + guideRuleConditionInfoVo.getFieldEn() + "\"]");
        sb.append(" ");
        sb.append(guideRuleConditionInfoVo.getOperator());
        sb.append(" ");
        // 根据字段类型 选择对应的输出
        sb.append(DroolsUtils.formatFiledValueByType(guideRuleConditionInfoVo.getFieldType(),
                guideRuleConditionInfoVo.getFieldValue()));
        // 添加到栈中
        drlInfoDto.getWhenStack().push(sb.toString());
    }

    /**
     * 处理关系型规则，即带有&& || 的规则
     *
     * @param guideRuleConditionInfoVo 规则条件对象
     * @param drlInfoDto drl对象
     */
    private void dealWithRelationshipRules(GuideRuleConditionInfoVo guideRuleConditionInfoVo,
                                           DrlInfoDto drlInfoDto) {
        // 开始递归执行
        dealRuleLhs(guideRuleConditionInfoVo.getChildren(), drlInfoDto);
        // 得到当前的子数量
        int size = guideRuleConditionInfoVo.getChildren().size();
        // 定义一个列表存储when条件
        List<String> whenList = Lists.newArrayList();
        // 开始遍历从栈中取值
        for (int i = 0; i < size; i++) {
            // 获取栈顶元素并删除
            whenList.add(0, drlInfoDto.getWhenStack().pop());
        }
        // 处理whenList
        String relationWhenStr = RuleConstant.LEFT_BRACKET
                + " "
                + StringUtils.join(whenList, " " + guideRuleConditionInfoVo.getLogical() + " ")
                + RuleConstant.RIGHT_BRACKET
                + " ";
        // 添加到栈上
        drlInfoDto.getWhenStack().push(relationWhenStr);
    }

    /**
     * 处理规则条件组
     *
     * @param guideRuleConditionInfoVo 规则条件对象
     * @param drlInfoDto drl对象
     */
    private void dealWithGroupRules(GuideRuleConditionInfoVo guideRuleConditionInfoVo,
                                    DrlInfoDto drlInfoDto) {
        // 这里需要注意一点，我们需要把规则组中的每一个规则都当做一个when来处理
        List<GuideRuleConditionInfoVo> children = guideRuleConditionInfoVo.getChildren();
        // 得到当前规则组的规则名称
        String ruleGroupName = guideRuleConditionInfoVo.getGuideRuleGroupName();
        // 命中即退出 命中不退出 在这里处理，要将条件下发到规则组中的每一个规则中
        GuideRuleGroupOutInfoVo guideRuleGroupOutInfoVo = guideRuleConditionInfoVo.getGuideRuleGroupOutInfoVo();
        // 命中即退出
        boolean hitToExitFlag = false;
        // 如果是命中即退出
        if (StringUtils.equals(guideRuleGroupOutInfoVo.getQuitConditionType(), RuleConstant.HIT_QUITE)) {
            hitToExitFlag = true;
        }
        // 生成一个列表存储规则组中每一个生成好的规则
        List<String> drlInfoList = Lists.newArrayList();
        // 定义一个列表用来存储当前的规则名称
        List<String> ruleNameList = Lists.newArrayList();
        // 用来存储当前的规则下标
        int index = 0;
        for (GuideRuleConditionInfoVo child : children) {
            // 继续递归处理children节点
            int priority = drlInfoDto.getPriority();
            // 生成规则名称
            String ruleName = ruleGroupName + "_" + (++index);
            ruleNameList.add(ruleName);
            // 先定义一个新的drlInfo
            // 因为我们这里需要将规则组中的drl全部生成完
            DrlInfoDto groupDrlInfo = new DrlInfoDto();
            // 赋值规则名称
            groupDrlInfo.setRuleName(ruleName);
            // 赋值优先级
            groupDrlInfo.setPriority(priority - 1);
            // 赋值当前复杂规则集的名称
            groupDrlInfo.setPreRuleName(drlInfoDto.getPreRuleName());
            // 这样处理可以让规则组中的每一个规则都是一个新的when
            dealRuleLhs(Lists.newArrayList(child), groupDrlInfo);
            // 如果当前的组中有组
            if (!CollectionUtils.isEmpty(groupDrlInfo.getDrlInfoList())) {
                drlInfoList.addAll(groupDrlInfo.getDrlInfoList());
                priority = priority - groupDrlInfo.getDrlInfoList().size() -1;
            }
            // 生成当前的ftl参数
            HashMap<String, Object> paramMap = Maps.newHashMap();
            paramMap.put("ruleGroupInfo", groupDrlInfo);
            // 赋值父规则的名称
            paramMap.put("parentRuleName", ruleGroupName);
            // 赋值优先级
            paramMap.put("salience", priority);
            // 命中即退出
            paramMap.put("hitToExitFlag", hitToExitFlag);
            // 利用模版开始生成drl信息
            String drlInfo = StringUtils.EMPTY;
            try {
                drlInfo = DroolsUtils.initRuleTemplate(ruleGroupFtlPath, ruleGroupFtlName, paramMap);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 将生成好的drl信息放到主对象中
            drlInfoList.add(drlInfo);
            drlInfoDto.setPriority(priority - 1);
        }
        // 这里无需添加import和attributes因为这是作为子规则去实现的，所以只用实现规则体
        // 执行到这里上面的所有的规则组中的规则都已经生成完了
        // 添加规则组的条件，即规则组的命中数量/命中或关系/命中&&关系
        String whenStr = DroolsUtils.buildRuleGroupWhen(guideRuleConditionInfoVo.getGuideRuleGroupOutInfoVo(),
                ruleGroupName, ruleNameList);
        drlInfoDto.getWhenStack().push("(" + whenStr + ")");
        drlInfoDto.getDrlInfoList().addAll(drlInfoList);
    }
}
